#!/usr/bin/env python3

'''
    Software License

    Copyright (C) 2021-05-24  Xoronos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''

'''
    Liabilities

    The software is provided "AS IS" without any warranty of any kind, either expressed,
    implied, or statutory, including, but not limited to, any warranty that the software
    will conform to specifications, any implied warranties of merchantability, fitness
    for a particular purpose, and freedom from infringement, and any warranty that the
    documentation will conform to the software, or any warranty that the software will
    be error free.

    In no event shall Xoronos be liable for any damages, including, but not limited to,
    direct, indirect, special or consequential damages, arising out of, resulting from,
    or in any way connected with this software, whether or not based upon warranty,
    contract, tort, or otherwise, whether or not injury was sustained by persons or
    property or otherwise, and whether or not loss was sustained from, or arose out of
    the results of, or use of, the software or services provided hereunder.
    
    To request the provided software under a different license you can contact us at
    support@xoronos.com
'''

import hashlib
import base64
import sys

# Define the base64 table (standard base64 alphabet)
base64_table = list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")

def prettify_b64(input_str, XNBITS):
    # Convert input string to a list for mutable operations
    str_list = list(input_str)
    
    # Initialize variables
    first_plus_index = -1
    first_slash_index = -1
    last_plus_index = -1
    last_slash_index = -1
    i = 0
    
    # Set string length based on XNBITS
    if XNBITS == 128:
        str_len = 22 + 1
    elif XNBITS == 256:
        str_len = 43 + 1
    else:
        raise ValueError("XNBITS must be 128 or 256")
    
    # Process string until '=' or str_len
    while i < str_len and str_list[i] != '=':
        if str_list[i] == '+':
            if first_plus_index == -1:
                first_plus_index = i
            else:
                str_list[i] = base64_table[last_plus_index]
            last_plus_index = i
        
        if str_list[i] == '/':
            if first_slash_index == -1:
                first_slash_index = i
            else:
                str_list[i] = base64_table[last_slash_index]
            last_slash_index = i
        
        i += 1
    
    # Ensure string length
    if len(str_list) < str_len:
        str_list.extend(['\0'] * (str_len - len(str_list)))
    elif len(str_list) > str_len:
        str_list = str_list[:str_len]
    
    # Handle special cases for final character and replacements
    if first_plus_index == first_slash_index:  # No special characters
        str_list[i] = base64_table[i]
    elif first_plus_index >= 0 and first_slash_index <= -1:  # Only '+'
        str_list[i] = base64_table[last_plus_index]
        str_list[first_plus_index] = base64_table[61]
    elif first_slash_index >= 0 and first_plus_index <= -1:  # Only '/'
        str_list[i] = base64_table[last_slash_index]
        str_list[first_slash_index] = base64_table[60]
    elif first_plus_index < first_slash_index:  # '+' before '/'
        str_list[i] = base64_table[last_plus_index]
        str_list[first_plus_index] = base64_table[last_slash_index]
        str_list[first_slash_index] = base64_table[59]
    else:  # '/' before '+'
        str_list[i] = base64_table[last_slash_index]
        str_list[first_slash_index] = base64_table[last_plus_index]
        str_list[first_plus_index] = base64_table[58]
    
    # Convert back to string, removing null characters
    return ''.join(str_list).rstrip('\0')

def compute_file_hash(file_path):
    # Initialize SHA-256 hash object
    sha256 = hashlib.sha256()
    
    # Read file in chunks to handle large files efficiently
    try:
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b''):
                sha256.update(chunk)
    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found.")
        sys.exit(1)
    except IOError as e:
        print(f"Error reading file '{file_path}': {e}")
        sys.exit(1)
    
    # Get the binary hash
    hash_bytes = sha256.digest()
    
    # Convert to base64
    base64_hash = base64.b64encode(hash_bytes).decode('utf-8')
    
    # Apply prettify_b64 for 256-bit hash
    prettified_hash = prettify_b64(base64_hash, XNBITS=256)
    
    return base64_hash, prettified_hash

def main():
    # Check for command-line argument
    if len(sys.argv) != 2:
        print("Usage: python hash_file_to_base64.py <file_path>")
        sys.exit(1)
    
    file_path = sys.argv[1]
    base64_hash, prettified_hash = compute_file_hash(file_path)
    
    print("")
    print(prettified_hash[0:11] + " " + prettified_hash[11:22] + " " + prettified_hash[22:33] + " " + prettified_hash[33:])
    print("")


if __name__ == "__main__":
    main()
